#ifndef CORE_H
#define CORE_H

#include <QString>
#include <QList>

class NamedSegment
{
public:
    int start;
    int end;
    QString name;
};

class Loop: public NamedSegment
{
public:
    int iterations;     // -1 means endless loop
    bool active;
};

class PracticeSegment: public Loop
{
public:
    float tempo;
    int pitch;
    bool enabled;
};

class Song
{
public:
    QString mpsFileName;
    QString songFilename;
    QString songName;
    QString format;
    int defaultPitch;
    int length;
    QList<Loop> loops;
    QList<PracticeSegment> segments;

    // adds a new practice segment
    bool AddSegment( void )
    {
        return InsertSegment( segments.size() - 1 );
    }

    // inserts a new practice segment
    bool InsertSegment( int prevSegmentIndex )
    {
        if( prevSegmentIndex < 0 || prevSegmentIndex >= segments.size() ) return false;
        PracticeSegment prevSegment = segments.at( prevSegmentIndex );

        if( prevSegmentIndex == segments.size() - 1 )
        {   // add
            int prevSegmentLength = prevSegment.end - prevSegment.start;

            if( prevSegmentLength <= 2000 )
            {   // I cannot allocate the new segment
                return false;
            }else
            {
                int segLength = prevSegmentLength/2;
                prevSegment.end = prevSegment.start + segLength;

                PracticeSegment newPs;
                newPs.name = "Practice Segment " + QString::number( segments.size() );
                newPs.pitch = defaultPitch;
                newPs.tempo = prevSegment.tempo;
                newPs.start = prevSegment.end;
                newPs.end = length;
                newPs.active = false;
                newPs.enabled = true;
                newPs.iterations = -1;

                segments.removeLast();
                segments.append( prevSegment );
                segments.append( newPs );
                return true;
            }
        }else
        {   // insert
            PracticeSegment nextSegment = segments.at( prevSegmentIndex + 1 );
            int totalLength = nextSegment.end - prevSegment.start;

            if( totalLength > 3000 )
            {   // I can allocate the segment

                // makes room for the new practice segment
                int segLength = totalLength/3;
                prevSegment.end = prevSegment.start + segLength;
                nextSegment.start = nextSegment.end - segLength;

                // builds the new segment
                PracticeSegment newPs;
                newPs.start = prevSegment.end;
                newPs.end = nextSegment.start;
                newPs.pitch = defaultPitch;
                newPs.name = "Practice Segment " + QString::number( segments.size() );
                newPs.tempo = ( prevSegment.tempo + nextSegment.tempo ) / 2;
                newPs.active = false;
                newPs.enabled = true;
                newPs.iterations = -1;

                segments.removeAt( prevSegmentIndex );
                segments.removeAt( prevSegmentIndex );
                segments.insert( prevSegmentIndex, nextSegment );
                segments.insert( prevSegmentIndex, prevSegment );
                segments.insert( prevSegmentIndex + 1, newPs );
                return true;
            }else
            {   // I cannot allocate the segment because each segment has a minimum length of 1 sec
                // and the total available time is less than 3 secs
                return false;
            }
        }
    }

    bool RemoveSegment( int index )
    {
        if( segments.size() == 1 ) return false;    // almost one segment must be present

        if( index == 0 )    // remove first
        {
            segments.removeFirst();
            segments[0].start = 0;
        } else if( index == segments.size() - 1 )   // remove last
        {
            segments[ segments.size() - 2 ].end = length;
            segments.removeLast();
        } else  // remove a segment in the middle
        {
            int segMiddle = segments[ index ].start + ( segments[ index ].end - segments[ index ].start ) / 2;
            segments[ index - 1 ].end = segMiddle;
            segments[ index + 1 ].start = segMiddle;
            segments.removeAt( index );
        }
        return true;
    }

    void ClearSegments( void )
    {
        PracticeSegment ps;
        ps.name = "Practice Segment 0";
        ps.start = 0;
        ps.end = length;
        ps.pitch = defaultPitch;
        ps.tempo = 1.0;
        ps.iterations = -1;
        ps.active = false;
        ps.enabled = true;

        segments.clear();
        segments.append( ps );
    }

    int GetSegmentIndexAtTime( int msecs, int* _offset = 0 )
    {
        if( msecs < 0 || msecs > length ) return -1;

        bool found = false;
        int i=0;
        int offset = 0;
        while( i<segments.size() && !found )
        {
            offset = msecs - segments.at(i).start;
            found = ( offset >= 0 && offset < ( segments.at(i).end - segments.at(i).start ) );
            i++;
        }

        if( _offset != 0)
        {
            *( _offset ) = offset;
        }

        if( !found )
        {
            return segments.size() - 1;
        }else
        {
            return --i;
        }
    }

    // returns the new segment index
    int SetSegmentStart( int segmentIndex, int msecs )
    {
        if( segmentIndex == 0 ) return -1;

        PracticeSegment* ps = &( segments[ segmentIndex ] );
        if( ps->end - 1000 < msecs ) return -1;              // segment must be at least 1 sec.

        // finds the segment that contains the new start position
        int startSegmentIndex = -1, i = 0;
        bool found = false;

        while( i<=segmentIndex && !found )
        {
            found = ( msecs >= segments[i].start && msecs < segments[i].end );
            i++;
        }

        // the segment is surely found
        startSegmentIndex = i - 1;

        if( startSegmentIndex == segmentIndex )
        {   // we need only to adjust the selected segment and the previous one
            ps->start = msecs;
            segments[ segmentIndex - 1 ].end = msecs;
            return segmentIndex;
        } else
        {
            PracticeSegment* startSegment = &( segments[ startSegmentIndex ] );
            if( msecs - startSegment->start < 1000 )
            {
                if( startSegmentIndex > 0 )
                {
                    PracticeSegment* tmpSegment = &( segments[ startSegmentIndex - 1 ] );

                    tmpSegment->end = msecs;
                    ps->start = msecs;
                } else
                {
                    ps->start = 0;
                }

                for( int i=startSegmentIndex; i<segmentIndex; i++ )
                {
                    segments.removeAt( startSegmentIndex );
                }
                return startSegmentIndex;
            }
            else
            {
                startSegment->end = msecs;
                ps->start = msecs;

                for( int i=startSegmentIndex + 1; i<segmentIndex; i++ )
                {
                    segments.removeAt( startSegmentIndex + 1 );
                }
                return startSegmentIndex + 1;
            }
        }
        return -1;
    }

    bool SetSegmentEnd( int segmentIndex, int msecs )
    {
        if( segmentIndex == segments.size() - 1 ) return false;

        PracticeSegment* ps = &( segments[ segmentIndex ] );
        if( msecs - ps->start < 1000 ) return false;

        // finds the final segment
        int i=segmentIndex;
        bool found = false;

        while( i<segments.size() && !found )
        {
            found = ( msecs >= segments[i].start && msecs < segments[i].end );
            i++;
        }

        int endSegmentIndex = i - 1;

        if( endSegmentIndex == segmentIndex )
        {   // we adjust the selected segment and the next one
            ps->end = msecs;
            segments[ segmentIndex + 1 ].start = msecs;
        }
        else
        {
            PracticeSegment* endSegment = &( segments[ endSegmentIndex ] );

            if( endSegment->end - msecs < 1000 )
            {
                if( endSegmentIndex == segments.size() - 1 )
                {
                    ps->end = length;
                }
                else
                {
                    PracticeSegment* tmpSegment = &( segments[ endSegmentIndex + 1 ] );
                    tmpSegment->start = msecs;
                    ps->end = msecs;
                }

                for( i=segmentIndex+1; i<=endSegmentIndex; i++ )
                {
                    segments.removeAt( segmentIndex + 1 );
                }
            }
            else
            {
                ps->end = msecs;
                endSegment->start = msecs;

                for( i=segmentIndex + 1; i<endSegmentIndex; i++ )
                {
                    segments.removeAt( segmentIndex + 1 );
                }
            }
        }
        return true;
    }

    void SetLoopStart( int loopIndex, int newStart )
    {
        if( loopIndex >= 0 && loopIndex < loops.size() )
        {
            if( newStart >= 0 && newStart < loops[ loopIndex ].end )
            {
                loops[ loopIndex ].start = newStart;
            }
        }
    }

    void SetLoopEnd( int loopIndex, int newEnd )
    {
        if( loopIndex >= 0 && loopIndex < loops.size() )
        {
            if( newEnd > loops[ loopIndex ].start && newEnd <= length )
            {
                loops[ loopIndex ].end = newEnd;
            }
        }
    }

    inline int GetLoopIndexAtTime( int msecs )
    {
        bool found = false;
        int i = 0;
        if( !found && i<loops.size() )
        {
            found = ( msecs >= loops[i].start && msecs <= loops[i].end );
            i++;
        }

        if( !found )
        {
            return -1;
        }
        else
        {
            return --i;
        }
    }

    inline Loop* GetFinishedLoop( int msecs )
    {
        bool found = false;
        int i = 0;
        Loop* l = 0;
        int tolerance = 50;
        while( !found && i<loops.size() )
        {
            l = &( loops[i] );
            if( l->active )
            {
                found = ( msecs >= ( l->end - tolerance ) && msecs <= ( l->end + tolerance ) );
            }
            i++;
        }

        if( !found )
        {
            return 0;
        }
        else
        {
            return l;
        }
    }

    Loop* AddLoop( void )
    {
        Loop l;
        l.start = 0;
        l.end = length;
        l.name = "Loop " + QString::number( loops.size() );
        l.iterations = -1;
        l.active = false;
        loops.append( l );
        return &( loops[ loops.size() - 1 ] );
    }

    void RemoveLoop( int index )
    {
        loops.removeAt( index );
    }
};

#endif // CORE_H
